<template>
  <main
    class="w-full min-h-screen bg-gradient-to-br from-indigo-500 via-blue-400 to-purple-200 overflow-x-hidden relative content-center">
    <p>click and drag</p>
    <section ref="hero" class="hero pricing-hero" data-block="pricing-hero">
      <div class="container">
        <div class="pricing-hero__content">
          <div class="pricing-hero__flair">
            <div class="pricing-hero__hand">
              <img class="pricing-hero__drag" src="https://assets.codepen.io/16327/hand-drag.png" alt="">
              <img class="pricing-hero__rock" src="https://assets.codepen.io/16327/hand-rock.png" alt="">
              <img class="pricing-hero__handle" src="https://assets.codepen.io/16327/2D-circle.png" alt="">
              <small>drag me</small>
            </div>

            <div class="image-preload" aria-hidden="true">
              <img data-key="combo" src="https://assets.codepen.io/16327/3D-combo.png" width="1" height="1"
                style="position: absolute; left: -9999px;" />
              <img data-key="cone" src="https://assets.codepen.io/16327/3D-cone.png" width="1" height="1"
                style="position: absolute; left: -9999px;" />
              <img data-key="hoop" src="https://assets.codepen.io/16327/3D-hoop.png" width="1" height="1"
                style="position: absolute; left: -9999px;" />
              <img data-key="keyframe" src="https://assets.codepen.io/16327/3D-keyframe.png" width="1" height="1"
                style="position: absolute; left: -9999px;" />
              <img data-key="semi" src="https://assets.codepen.io/16327/3D-semi.png" width="1" height="1"
                style="position: absolute; left: -9999px;" />
              <img data-key="spiral" src="https://assets.codepen.io/16327/3D-spiral.png" width="1" height="1"
                style="position: absolute; left: -9999px;" />
              <img data-key="squish" src="https://assets.codepen.io/16327/3D-squish.png" width="1" height="1"
                style="position: absolute; left: -9999px;" />
              <img data-key="triangle" src="https://assets.codepen.io/16327/3D-triangle.png" width="1" height="1"
                style="position: absolute; left: -9999px;" />
              <img data-key="tunnel" src="https://assets.codepen.io/16327/3D-tunnel.png" width="1" height="1"
                style="position: absolute; left: -9999px;" />
              <img data-key="wat" src="https://assets.codepen.io/16327/3D-poly.png" width="1" height="1"
                style="position: absolute; left: -9999px;" />
            </div>
            <div class="explosion-preload" aria-hidden="true">
              <img data-key="blue-circle" src="https://assets.codepen.io/16327/2D-circles.png"
                style="position: absolute; left: -9999px;" />
              <img data-key="green-keyframe" src="https://assets.codepen.io/16327/2D-keyframe.png"
                style="position: absolute; left: -9999px;" />
              <img data-key="orange-lightning" src="https://assets.codepen.io/16327/2D-lightning.png"
                style="position: absolute; left: -9999px;" />
              <img data-key="orange-star" src="https://assets.codepen.io/16327/2D-star.png"
                style="position: absolute; left: -9999px;" />
              <img data-key="purple-flower" src="https://assets.codepen.io/16327/2D-flower.png"
                style="position: absolute; left: -9999px;" />
              <img data-key="cone" src="https://assets.codepen.io/16327/3D-cone.png"
                style="position: absolute; left: -9999px;" />
              <img data-key="keyframe" src="https://assets.codepen.io/16327/3D-spiral.png"
                style="position: absolute; left: -9999px;" />
              <img data-key="spiral" src="https://assets.codepen.io/16327/3D-spiral.png"
                style="position: absolute; left: -9999px;" />
              <img data-key="tunnel" src="https://assets.codepen.io/16327/3D-tunnel.png"
                style="position: absolute; left: -9999px;" />
              <img data-key="hoop" src="https://assets.codepen.io/16327/3D-hoop.png"
                style="position: absolute; left: -9999px;" />
              <img data-key="semi" src="https://assets.codepen.io/16327/3D-semi.png"
                style="position: absolute; left: -9999px;" />
            </div>
          </div>
        </div>
        <svg ref="canvas" class="pricing-hero__canvas"></svg>
        <div ref="proxy" class="pricing-hero__proxy"></div>
      </div>
    </section>
  </main>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue'
const { $gsap, $Observer, $ScrollTrigger, $CustomWiggle, $CustomEase, $Physics2DPlugin } = useNuxtApp()
import type { gsap } from 'gsap'


const hero = ref<HTMLElement | null>(null)
const canvas = ref<SVGSVGElement | null>(null)
const proxy = ref<HTMLElement | null>(null)

interface ConfettiElements {
  hand: HTMLElement | null
  instructions: HTMLElement | null
  rock: HTMLImageElement | null
  drag: HTMLImageElement | null
  handle: HTMLImageElement | null
  canvas: SVGSVGElement | null
  proxy: HTMLElement | null
  preloadImages: NodeListOf<HTMLImageElement>
  xplodePreloadImages: NodeListOf<HTMLImageElement>
}

class ConfettiCannon {
  private el: HTMLElement
  private hero: HTMLElement
  private elements: ConfettiElements
  private isDrawing = false
  private animationIsOk: boolean
  private wiggle: any
  private clamper: (value: number) => number
  private xSetter!: gsap.QuickToFunc
  private ySetter!: gsap.QuickToFunc
  private imageMap: Record<string, HTMLImageElement> = {}
  private imageKeys: string[] = []
  private explosionMap: Record<string, HTMLImageElement> = {}
  private explosionKeys: string[] = []
  private currentLine: SVGLineElement | null = null
  private startImage: SVGImageElement | null = null
  private circle: SVGCircleElement | null = null
  private startX = 0
  private startY = 0
  private lastDistance = 0

  constructor(el: HTMLElement) {
    this.el = el
    this.hero = el
    this.animationIsOk = window.matchMedia("(prefers-reduced-motion: no-preference)").matches

    this.elements = {
      hand: null,
      instructions: null,
      rock: null,
      drag: null,
      handle: null,
      canvas: null,
      proxy: null,
      preloadImages: el.querySelectorAll(".image-preload img"),
      xplodePreloadImages: el.querySelectorAll(".explosion-preload img")
    }

    this.clamper = $gsap.utils.clamp(1, 100)
    this.wiggle = $CustomWiggle.create("myWiggle", { wiggles: 6 })
  }

  init() {
    this.elements.hand = this.hero.querySelector(".pricing-hero__hand")
    this.elements.instructions = this.hero.querySelector(".pricing-hero__hand small")
    this.elements.rock = this.hero.querySelector(".pricing-hero__rock")
    this.elements.drag = this.hero.querySelector(".pricing-hero__drag")
    this.elements.handle = this.hero.querySelector(".pricing-hero__handle")
    this.elements.canvas = this.hero.querySelector(".pricing-hero__canvas")
    this.elements.proxy = this.hero.querySelector(".pricing-hero__proxy")

    // 初始化图片映射
    this.elements.preloadImages.forEach((img) => {
      const key = img.dataset.key as string
      this.imageMap[key] = img
      this.imageKeys.push(key)
    })

    // 初始化爆炸效果图片映射
    this.elements.xplodePreloadImages.forEach((img) => {
      const key = img.dataset.key as string
      this.explosionMap[key] = img
      this.explosionKeys.push(key)
    })

    this.xSetter = $gsap.quickTo(this.elements.hand as HTMLElement, "x", { duration: 0.1 })
    this.ySetter = $gsap.quickTo(this.elements.hand as HTMLElement, "y", { duration: 0.1 })

    this.setPricingMotion()
    this.initObserver()
    this.initEvents()
  }

  private setPricingMotion() {
    if (this.elements.hand) {
      $gsap.set(this.elements.hand, { xPercent: -50, yPercent: -50 })
    }
  }

  private initObserver() {
    if (!this.animationIsOk || !this.elements.proxy) return

    if ($ScrollTrigger.isTouch === 1) {
      $Observer.create({
        target: this.elements.proxy,
        type: "touch",
        onPress: (e) => {
          this.createExplosion(e?.x || 0, e?.y || 0, 400)
        }
      })
    } else {
      $Observer.create({
        target: this.elements.proxy,
        type: "pointer",
        onPress: (e) => this.startDrawing(e),
        onDrag: (e) => this.isDrawing && this.updateDrawing(e),
        onDragEnd: (e) => this.clearDrawing(e),
        onRelease: (e) => this.clearDrawing(e)
      })
    }
  }

  private initEvents() {
    if (!this.animationIsOk || $ScrollTrigger.isTouch === 1 || !this.elements.hand) return

    this.hero.style.cursor = "none"

    this.hero.addEventListener("mouseenter", (e) => {
      const mouseEvent = e as MouseEvent
      $gsap.set(this.elements.hand, { opacity: 1 })
      this.xSetter(mouseEvent.x)
      this.ySetter(mouseEvent.y)
    })

    this.hero.addEventListener("mouseleave", () => {
      $gsap.set(this.elements.hand, { opacity: 0 })
    })

    this.hero.addEventListener("mousemove", (e) => {
      const mouseEvent = e as MouseEvent
      this.xSetter(mouseEvent.x)
      this.ySetter(mouseEvent.y)
    })

    $gsap.delayedCall(1, () => {
      this.createExplosion(window.innerWidth / 2, window.innerHeight / 2, 600)
    })
  }

  private startDrawing(e: any) {
    this.isDrawing = true

    if (this.elements.instructions) {
      $gsap.set(this.elements.instructions, { opacity: 0 })
    }

    this.startX = e.x
    this.startY = e.y + window.scrollY

    // Create line
    this.currentLine = document.createElementNS("http://www.w3.org/2000/svg", "line")
    this.currentLine.setAttribute("x1", this.startX.toString())
    this.currentLine.setAttribute("y1", this.startY.toString())
    this.currentLine.setAttribute("x2", this.startX.toString())
    this.currentLine.setAttribute("y2", this.startY.toString())
    this.currentLine.setAttribute("stroke", "#fffce1")
    this.currentLine.setAttribute("stroke-width", "2")
    this.currentLine.setAttribute("stroke-dasharray", "4")

    this.circle = document.createElementNS("http://www.w3.org/2000/svg", "circle")
    this.circle.setAttribute("cx", this.startX.toString())
    this.circle.setAttribute("cy", this.startY.toString())
    this.circle.setAttribute("r", "30")
    this.circle.setAttribute("fill", "#0e100f")

    // Create image at start point
    const randomKey = $gsap.utils.random(this.imageKeys)
    const original = this.imageMap[randomKey]
    const clone = document.createElementNS("http://www.w3.org/2000/svg", "image")

    clone.setAttribute("x", (this.startX - 25).toString())
    clone.setAttribute("y", (this.startY - 25).toString())
    clone.setAttribute("width", "50")
    clone.setAttribute("height", "50")
    clone.setAttributeNS("http://www.w3.org/1999/xlink", "href", original.src)

    this.startImage = clone

    if (this.elements.canvas) {
      this.elements.canvas.appendChild(this.currentLine)
      this.elements.canvas.appendChild(this.circle)
      this.elements.canvas.appendChild(this.startImage)
    }

    if (this.elements.drag) {
      $gsap.set(this.elements.drag, { opacity: 1 })
    }
    if (this.elements.handle) {
      $gsap.set(this.elements.handle, { opacity: 1 })
    }
    if (this.elements.rock) {
      $gsap.set(this.elements.rock, { opacity: 0 })
    }
  }

  private updateDrawing(e: any) {
    if (!this.currentLine || !this.startImage || !this.circle || !this.elements.hand) return

    const cursorX = e.x
    const cursorY = e.y + window.scrollY

    const dx = cursorX - this.startX
    const dy = cursorY - this.startY

    const distance = Math.sqrt(dx * dx + dy * dy)
    const shrink = (distance - 30) / distance

    let x2 = this.startX + dx * shrink
    let y2 = this.startY + dy * shrink

    if (distance < 30) {
      x2 = this.startX
      y2 = this.startY
    }

    const angle = Math.atan2(dy, dx) * (180 / Math.PI)

    $gsap.to(this.currentLine, {
      attr: { x2, y2 },
      duration: 0.1,
      ease: "none"
    })

    // Eased scale (starts fast, slows down)
    const raw = distance / 100
    const eased = Math.pow(raw, 0.5)
    const clamped = this.clamper(eased)

    $gsap.set([this.startImage, this.circle], {
      scale: clamped,
      rotation: `${angle + -45}_short`,
      transformOrigin: "center center"
    })

    // Move & rotate hand
    $gsap.to(this.elements.hand, {
      rotation: `${angle + -90}_short`,
      duration: 0.1,
      ease: "none"
    })

    this.lastDistance = distance
  }

  createExplosion(x: number, y: number, distance = 100) {
    const count = Math.round($gsap.utils.clamp(3, 100, distance / 20))
    const angleSpread = Math.PI * 2
    const explosion = $gsap.timeline()
    const gravity = 5
    const speed = $gsap.utils.mapRange(0, 500, 0.3, 1.5, distance)
    const sizeRange = $gsap.utils.mapRange(0, 500, 20, 60, distance)

    for (let i = 0; i < count; i++) {
      const randomKey = $gsap.utils.random(this.explosionKeys)
      const original = this.explosionMap[randomKey]
      const img = original.cloneNode(true) as HTMLImageElement

      img.className = "explosion-img"
      img.style.position = "absolute"
      img.style.pointerEvents = "none"
      img.style.height = `${$gsap.utils.random(20, sizeRange)}px`
      img.style.left = `${x}px`
      img.style.top = `${y}px`
      img.style.zIndex = "4"

      this.hero.appendChild(img)

      const angle = Math.random() * angleSpread
      const velocity = $gsap.utils.random(500, 1500) * speed

      explosion
        .to(
          img,
          {
            physics2D: {
              angle: angle * (180 / Math.PI),
              velocity: velocity,
              gravity: 3000
            },
            rotation: $gsap.utils.random(-180, 180),
            duration: 1 + Math.random()
          },
          0
        )
        .to(
          img,
          {
            opacity: 0,
            duration: 0.2,
            ease: "power1.out",
            onComplete: () => img.remove()
          },
          1
        )
    }

    return explosion
  }

  private clearDrawing(e: any) {
    if (!this.isDrawing || !this.elements.canvas) return
    this.createExplosion(this.startX, this.startY, this.lastDistance)

    if (this.elements.drag) {
      $gsap.set(this.elements.drag, { opacity: 0 })
    }
    if (this.elements.handle) {
      $gsap.set(this.elements.handle, { opacity: 0 })
    }
    if (this.elements.rock) {
      $gsap.set(this.elements.rock, { opacity: 1 })
    }

    if (this.elements.rock && this.elements.hand && this.elements.instructions && this.elements.drag) {
      $gsap.to(this.elements.rock, {
        duration: 0.4,
        rotation: "+=30",
        ease: "myWiggle",
        onComplete: () => {
          $gsap.set(this.elements.rock, { opacity: 0 })
          $gsap.set(this.elements.hand, { rotation: 0, overwrite: "auto" })
          $gsap.to(this.elements.instructions, { opacity: 1 })
          $gsap.set(this.elements.drag, { opacity: 1 })
        }
      })
    }

    this.isDrawing = false

    // Clear all elements from SVG and reset references
    this.elements.canvas.innerHTML = ""
    this.currentLine = null
    this.startImage = null
    this.circle = null
  }
}

onMounted(() => {
  if (hero.value) {
    const cannon = new ConfettiCannon(hero.value)
    cannon.init()
  }
})
</script>

<style scoped>
.w-full {
  width: 100%;
}

.min-h-screen {
  min-height: 100vh;
}

.bg-gradient-to-br {
  background-image: linear-gradient(to bottom right, var(--tw-gradient-stops));
}

.from-indigo-500 {
  --tw-gradient-from: #6366f1;
  --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to, rgb(99 102 241 / 0));
}

.via-blue-400 {
  --tw-gradient-via: #60a5fa;
  --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-via), var(--tw-gradient-to, rgb(96 165 250 / 0));
}

.to-purple-200 {
  --tw-gradient-to: #e9d5ff;
}

.overflow-x-hidden {
  overflow-x: hidden;
}

.relative {
  position: relative;
}

.content-center {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  /* flex-wrap: wrap; */
}

.hero.pricing-hero {
  position: relative;
  width: 100%;
  height: 100vh;
  overflow: hidden;
}

.container {
  width: 100%;
  height: 100%;
  position: relative;
}

.pricing-hero__content {
  width: 100%;
  height: 100%;
  position: relative;
}

.pricing-hero__flair {
  position: absolute;
  width: 100%;
  height: 100%;
  top: 0;
  left: 0;
}

.pricing-hero__hand {
  position: absolute;
  pointer-events: none;
  z-index: 3;
  opacity: 0;
  transform-origin: center center;
}

.pricing-hero__rock,
.pricing-hero__drag,
.pricing-hero__handle {
  position: absolute;
  pointer-events: none;
}

.pricing-hero__canvas {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  pointer-events: none;
}

.pricing-hero__proxy {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  pointer-events: auto;
}

.explosion-img {
  will-change: transform;
}
</style>